#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//
// types
//
#ifndef byte
typedef unsigned char byte;
#endif // byte

#ifndef word
typedef unsigned short word;
#endif // word

#define COPY(dest, src) //copy matrix

#define TRANSFORM(matrix, vector) //transform vector by matrix
#define ROTATE_ONLY(matrix, vector) //rotate vector by matrix

#define INVERSE(result, input) //inverse matrix
#define IDENTITY(mat) //set identity
#define TRANSLATE(mat, transl) //translate matrix
#define ROTATE(mat, rot) //rotate matrix
#define MULTIPLY3(result, mat1, mat2) //multiply matrix

typedef struct
{
  char *fname;
  word w;
  word h;
  char *data;
} myTexture;

myTexture *getTexture(char *fname)
{
	return 0;
}

typedef struct
{
  float ambient[4];
  float diffuse[4];
  float specular[4];
  float emissive[4];
  float shininess;
  float transparency;
  myTexture *texture;
  myTexture *alphamap;
} myMaterial;

myMaterial *importMaterials(word *nMaterials, FILE *f)
{
	char tmpBuf[128];
	fread(nMaterials, sizeof(word), 1, f);
	myMaterial *materialsData = malloc(sizeof(myMaterial)*(*nMaterials));

	int m;
	for(m=0; m<*nMaterials; m++) {
		fread(materialsData[m].ambient, sizeof(float), 4, f);
		fread(materialsData[m].diffuse, sizeof(float), 4, f);
		fread(materialsData[m].specular, sizeof(float), 4, f);
		fread(materialsData[m].emissive, sizeof(float), 4, f);
		fread(&materialsData[m].shininess, sizeof(float), 1, f);
		fread(&materialsData[m].transparency, sizeof(float), 1, f);

		fgets(tmpBuf,128,f);
		materialsData[m].texture = getTexture(tmpBuf);
		fgets(tmpBuf,128,f);
		materialsData[m].alphamap = getTexture(tmpBuf);
	}

	return materialsData;
}

typedef struct {
  float time;
  float rotation[3];
  float translation[3];
} myKeyframe;

typedef struct {
  int parentIndex;
  float rotation[3];
  float translation[3];
  int numKeyframes;
  myKeyframe *keyframes;
  float rel[4][4];
  float abs[4][4];
  float fin[4][4];
} myJoint;

void setupJoints(myJoint *joints, word nJoints)
{
	int i;

	for(i=0; i<nJoints; i++) {
		IDENTITY(joints[i].rel);
		TRANSLATE(joints[i].rel,joints[i].translation);
		ROTATE(joints[i].rel,joints[i].rotation);

		int parentIndex = joints[i].parentIndex;
		if(parentIndex != -1) {
			//ABS = PARENT_ABS * REL
			MULTIPLY3(joints[i].abs, joints[parentIndex].abs, joints[i].rel);
		} else {
			COPY(joints[i].abs, joints[i].rel);
		}
	}
}

myJoint *importJoints(word *nJoints, FILE *f)
{
	fread(nJoints, sizeof(word), 1, f);
	myJoint *jointsData = malloc(sizeof(myJoint)*(*nJoints));

	int i;
	for(i=0; i<*nJoints; i++) {
		fread(&jointsData[i].parentIndex, sizeof(int), 1, f);
		fread(jointsData[i].rotation, sizeof(float), 3, f);
		fread(jointsData[i].translation, sizeof(float), 3, f);
		fread(&jointsData[i].numKeyframes, sizeof(int), 1, f);
		jointsData[i].keyframes = malloc(sizeof(myKeyframe)*jointsData[i].numKeyframes);
		fread(jointsData[i].keyframes, sizeof(myKeyframe), jointsData[i].numKeyframes, f);
	}

	return jointsData;
}

typedef struct {
  float pos[3];
  float normal[3];
  float s;
  float t;
} myVertex;

typedef struct {
  myVertex v1;
  myVertex v2;
  myVertex v3;
} myTriangle;

typedef struct {
  word v1;
  word v2;
  word v3;
} __attribute__ ((__packed__)) myTriangleIndexed;

typedef struct {
  word materialIndex;
  word numTriangles;
  myTriangleIndexed *triangles;
} myGroupIndexed;

typedef struct {
  word nGroups;
  myGroupIndexed *groups;

  word nVertices;
  myVertex *vertices;
  word *bonesIds;

  word nMaterials;
  myMaterial *materials;

  word nJoints;
  myJoint *joints;
} myModelIndexed;

typedef struct {
  word materialIndex;
  word numTriangles;
  myTriangle *triangles;
} myFixedGroup;

typedef struct {
  word nGroups;
  myFixedGroup *groups;

  word nMaterials;
  myMaterial *materials;
} myFixedModel;

myFixedModel *createFixedModel(myModelIndexed *model)
{
	int i;

	myFixedModel *fixedModel = (myFixedModel *)malloc(sizeof(myFixedModel));
	fixedModel->nGroups = model->nGroups;
	fixedModel->groups = (myFixedGroup *)malloc(sizeof(myFixedGroup)*model->nGroups);

	for(i=0; i<model->nGroups; i++) {
		fixedModel->groups[i].materialIndex = model->groups[i].materialIndex;
		fixedModel->groups[i].numTriangles = model->groups[i].numTriangles;
		fixedModel->groups[i].triangles = (myTriangle *)malloc(sizeof(myTriangle)*model->groups[i].numTriangles);
	}

//aponta para os mesmos dados de material
	fixedModel->nMaterials = model->nMaterials;
	fixedModel->materials = model->materials;

	return fixedModel;
}

myFixedModel *convertToFixedModel(myModelIndexed *model)
{
	myFixedModel *fixedModel;
	int i,j,nVerts;

	fixedModel = createFixedModel(model);

	for(i=0; i<model->nGroups; i++) {
		for(j=0; j<model->groups[i].numTriangles; j++) {
			memcpy(&fixedModel->groups[i].triangles[j].v1, &model->vertices[model->groups[i].triangles[j].v1], sizeof(myVertex));
			memcpy(&fixedModel->groups[i].triangles[j].v2, &model->vertices[model->groups[i].triangles[j].v2], sizeof(myVertex));
			memcpy(&fixedModel->groups[i].triangles[j].v3, &model->vertices[model->groups[i].triangles[j].v3], sizeof(myVertex));
		}
		free(model->groups[i].triangles);
	}

	for(i=0; i<model->nJoints; i++) {
		free(model->joints[i].keyframes);
	}

	free(model->groups);
	free(model->vertices);
	free(model->bonesIds);
	free(model->joints);

	free(model);

	return fixedModel;
}

void animateVertex(myVertex *vIn, myVertex *vOut, myJoint *joints, word boneId)
{
}

void animateToFixedModel(myModelIndexed *mi, myFixedModel *fm)
{
	int i,j;
	myVertex **animatedTo = (myVertex **)malloc(sizeof(myVertex *)*mi->nVertices);
	memset(animatedTo, 0, sizeof(myVertex *)*mi->nVertices);

	for(i=0; i<mi->nGroups; i++) {
		for(j=0; j<mi->groups[i].numTriangles; j++) {
			word idxV1 = mi->groups[i].triangles[j].v1;
			if(animatedTo[idxV1]) memcpy(&fm->groups[i].triangles[j].v1, animatedTo[idxV1], sizeof(myVertex));
			else {
				animatedTo[idxV1] = &fm->groups[i].triangles[j].v1;
				animateVertex(&mi->vertices[idxV1], &fm->groups[i].triangles[j].v1, mi->joints, mi->bonesIds[idxV1]);
			}

			word idxV2 = mi->groups[i].triangles[j].v2;
			if(animatedTo[idxV2]) memcpy(&fm->groups[i].triangles[j].v2, animatedTo[idxV2], sizeof(myVertex));
			else {
				animatedTo[idxV2] = &fm->groups[i].triangles[j].v2;
				animateVertex(&mi->vertices[idxV2], &fm->groups[i].triangles[j].v2, mi->joints, mi->bonesIds[idxV2]);
			}

			word idxV3 = mi->groups[i].triangles[j].v3;
			if(animatedTo[idxV3]) memcpy(&fm->groups[i].triangles[j].v3, animatedTo[idxV3], sizeof(myVertex));
			else {
				animatedTo[idxV3] = &fm->groups[i].triangles[j].v3;
				animateVertex(&mi->vertices[idxV3], &fm->groups[i].triangles[j].v3, mi->joints, mi->bonesIds[idxV3]);
			}
		}
	}

	free(animatedTo);
}

myModelIndexed *importDataIndexed(FILE *f)
{
	myModelIndexed *model = malloc(sizeof(myModelIndexed));
	fread(&model->nGroups, sizeof(word), 1 ,f);
	model->groups = malloc(sizeof(myGroupIndexed)*model->nGroups);
	int g;
	for(g=0; g<model->nGroups; g++) {
		fread(&model->groups[g].materialIndex, sizeof(word), 1, f);
		fread(&model->groups[g].numTriangles, sizeof(word), 1, f);

		model->groups[g].triangles = malloc(sizeof(myTriangleIndexed)*model->groups[g].numTriangles);
		fread(model->groups[g].triangles, sizeof(myTriangleIndexed), model->groups[g].numTriangles, f);
	}

	fread(&model->nVertices, sizeof(word), 1, f);
	model->vertices = malloc(sizeof(myVertex)*model->nVertices);
	fread(model->vertices, sizeof(myVertex), model->nVertices, f);

	model->bonesIds = malloc(sizeof(word)*model->nVertices);
	fread(model->bonesIds, sizeof(word), model->nVertices, f);

	model->materials = importMaterials(&model->nMaterials, f);
	model->joints = importJoints(&model->nJoints, f);

	setupJoints(model->joints, model->nJoints);

	int i;
	for(i=0; i<model->nVertices; i++) {
		float inv[4][4];

		word boneId = model->bonesIds[i];
		if(boneId != -1) {
			INVERSE(inv, model->joints[boneId].abs);
			TRANSFORM(inv,model->vertices[i].pos);
			ROTATE_ONLY(inv,model->vertices[i].normal);
		}
	}
}

typedef struct {
	float startTime;
	float duration;
	float elapsed;
} myAnim;

void advanceAnim(myAnim *anim, float elapsed)
{
	anim->elapsed += elapsed;

//	while(anim->elapsed>anim->duration) anim->elapsed -= anim->duration;
	int n = anim->elapsed/anim->duration;
	anim->elapsed -= n*anim->duration;
}

void animateJoints(myJoint *joints, word numJoints, myAnim *anim)
{
	int i;
	float zeroFrame[3] = {0.0f, 0.0f, 0.0f};
	float time = anim->startTime + anim->elapsed;

	for(i=0; i<numJoints; i++) {
		int frameIndex = 0;

		while(time > joints[i].keyframes[frameIndex].time) {
			++frameIndex;
			if(frameIndex==joints[i].numKeyframes) break;
		}

		float t0, t1;
		float *trans0,*trans1;
		float *rot0,*rot1;

		if(frameIndex>0) {
			t0 = joints[i].keyframes[frameIndex-1].time;
			trans0 = joints[i].keyframes[frameIndex-1].translation;
			rot0 = joints[i].keyframes[frameIndex-1].rotation;

			t1 = joints[i].keyframes[frameIndex].time;
			trans1 = joints[i].keyframes[frameIndex].translation;
			rot1 = joints[i].keyframes[frameIndex].rotation;			
		} else if(frameIndex==joints[i].numKeyframes) {
			t0 = 0;
			trans0 = zeroFrame;
			rot0 = zeroFrame;

			t1 = time;
			trans1 = joints[i].keyframes[frameIndex-1].translation;
			rot1 = joints[i].keyframes[frameIndex-1].rotation;
		} else {
			t0 = 0;
			trans0 = zeroFrame;
			rot0 = zeroFrame;

			t1 = joints[i].keyframes[0].time;
			trans1 = joints[i].keyframes[0].translation;
			rot1 = joints[i].keyframes[0].rotation;
		}

		float m = (time-t0)/(t1-t0);
		float rot[3] = { (rot0[0]+rot1[0])*m, (rot0[1]+rot1[1])*m, (rot0[2]+rot1[2])*m };
		float trans[3] = { (trans0[0]+trans1[0])*m, (trans0[1]+trans1[1])*m, (trans0[2]+trans1[2])*m };

		float keyframeTransform[4][4];
		IDENTITY(keyframeTransform);
		TRANSLATE(keyframeTransform, trans);
		ROTATE(keyframeTransform, rot);

		if(joints[i].parentIndex != -1) {
			float tmp[4][4]
			MULTIPLY3(tmp,joints[i].rel,keyframeTransform);
			MULTIPLY3(joints[i].fin,joints[joints[i].parentIndex].fin,tmp);
		} else {
			MULTIPLY3(joints[i].fin,joints[i].rel,keyframeTransform);
		}
	}
}

void main(int argc, char **argv)
{
	FILE *fIn = fopen(argv[1],"r");

	
}
